package eu.webhib.frmwrk.persistence;

import eu.webhib.frmwrk.utils.ExtendedProperties;
import javax.persistence.*;
import org.apache.log4j.Logger;
import org.hibernate.proxy.HibernateProxyHelper;

/*
 * see http://javanotepad.blogspot.com/2007/08/managing-jpa-entitymanager-lifecycle.html
 */
public class PersistenceMngr {

   private static final Logger log = Logger.getLogger(PersistenceMngr.class);
   //
   private static final String DEFAULT_PU_NAME = "memory";
   private static EntityManagerFactory emf;
   private static final ThreadLocal<EntityManager> em;

   static {
      emf = null;
      em = new ThreadLocal<EntityManager>();
   }

   private PersistenceMngr() {
   }

   public static boolean identicalClass(Object obj1, Object obj2) {
      return HibernateProxyHelper.getClassWithoutInitializingProxy(obj1)
              == HibernateProxyHelper.getClassWithoutInitializingProxy(obj2);
   }

   /**
    * EntityManagerFactory
    * @param persistenceUnit The persistence unit as set up in the persistence.xml file
    * @param properties Further properties overwriting the properties given in the persistence.xml file
    *                   e.g. database user and password
    */
   public static void createEntityManagerFactory(String persistenceUnit, ExtendedProperties properties) {
      log.info("--- start: create EntityManagerFactory for persistenceUnit: " + persistenceUnit);
      if (persistenceUnit == null) {
         persistenceUnit = DEFAULT_PU_NAME;
      }
      try {
         /** map user friendly properties keys */
         if (properties.get("database.user") != null) {
            properties.setProperty("javax.persistence.jdbc.user", properties.get("database.user"));
         }
         if (properties.get("database.password") != null) {
            properties.setProperty("javax.persistence.jdbc.user", properties.get("database.password"));
         }
         /** Create the EntityManagerFactory */
         emf = Persistence.createEntityManagerFactory(persistenceUnit, properties);
      } catch (Throwable ex) {
         log.error("EntityManagerFactory creation failed for PU: " + persistenceUnit, ex);
         throw new ExceptionInInitializerError(ex);
      }
      log.info("--- end: create EntityManagerFactory for persistenceUnit: " + persistenceUnit);
   }

   public static EntityManagerFactory getEntityManagerFactory() {
      log.info("getEntityManagerFactory");
      return emf;
   }

   public static void closeEntityManagerFactory() {
      log.info("closeEntityManagerFactory");
      emf.close();
   }

   /*
    * EntityManager
    */
   private static void createEntityManager() {
      log.info("createEntityManager");
      em.set(emf.createEntityManager());
   }

   public static EntityManager getEntityManager() {
      log.debug("getEntityManager");
      if (em.get() == null) {
         createEntityManager();
      }
      return em.get();
   }

   public static void closeEntityManager() {
      if (em.get() != null) {
         EntityTransaction tr = getCurrentTransaction();
         if (tr.isActive()) {
            log.error(">>>>>> Transaction not committed by application, do it now >>>>>>>");
            commitTransaction();
         }
         log.info("closeEntityManager");
         em.get().close();
         em.set(null);
      }
   }

   /*
    * Transaction Management
    */
   public static void beginTransaction() {
      EntityTransaction tr = getCurrentTransaction();
      if (!tr.isActive()) {
         log.debug(">>> beginTransaction");
         tr.begin();
      }
   }

   public static void commitTransaction() {
      /** avoid creating an entity manager if nothing is to commit */
      if (em.get() == null) {
         return;
      }
      EntityTransaction tr = getCurrentTransaction();
      if (tr.isActive()) {
         if (tr.getRollbackOnly()) {
            rollbackTransaction();
         } else {
            log.debug("<<< commitTransaction");
            tr.commit();
         }
      }
   }

   public static void setRollbackTransaction() {
      EntityTransaction tr = getCurrentTransaction();
      log.debug("setRollbackTransaction");
      tr.setRollbackOnly();
   }

   public static void rollbackTransaction() {
      EntityTransaction tr = getCurrentTransaction();
      if (tr.isActive()) {
         log.debug("<<< rollbackTransaction");
         tr.rollback();
      }
   }

   private static EntityTransaction getCurrentTransaction() {
      EntityTransaction tr = getEntityManager().getTransaction();
      log.debug("getTransaction: " + tr);
      return tr;
   }
}
